<?xml version="1.0" encoding="UTF-8"?>
<!--
   - xstutil.xsl
   - Utilities for tag preprocessing
   -
   - Copyright (c) 2011 Erik Hennum. All Rights Reserved.
   -
   - Licensed under the Apache License, Version 2.0 (the "License");
   - you may not use this file except in compliance with the License.
   - You may obtain a copy of the License at
   -
   - http://www.apache.org/licenses/LICENSE-2.0
   -
   - Unless required by applicable law or agreed to in writing, software
   - distributed under the License is distributed on an "AS IS" BASIS,
   - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   - See the License for the specific language governing permissions and
   - limitations under the License.
   -
   - The use of the Apache License does not indicate that this work is
   - associated with the Apache Software Foundation.
   -
  -->
<xsl:stylesheet version="2.0"
    xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
    xmlns:osl = "file:///www.w3.org/1999/XSL/Transform"
    xmlns:fn  = "http://www.w3.org/2005/xpath-functions"
    xmlns:xs  = "http://www.w3.org/2001/XMLSchema"
    xmlns:xst = "http://code.google.com/p/xstag/ns/2011/"
    >

<xsl:namespace-alias stylesheet-prefix="osl" result-prefix="xsl"/>

<xsl:variable name="xst:module-uri" as="xs:string?"
    select="fn:document-uri(fn:root(/))"/>

<xsl:variable name="APOSTROPHE"     as="xs:string">'</xsl:variable>
<xsl:variable name="QUOTATION_MARK" as="xs:string">"</xsl:variable>

<xsl:variable name="PARAM_NAME"       as="xs:integer" select="2"/>
<xsl:variable name="PARAM_TYPE_START" as="xs:integer" select="3"/>
<xsl:variable name="PARAM_TYPE_END"   as="xs:integer" select="4"/>
<xsl:variable name="RETURN_TYPE"      as="xs:integer" select="5"/>

<xsl:variable name="SEQUENCE_START" select="10"/>
<xsl:variable name="SEQUENCE_END"   select="11"/>
<xsl:variable name="STRING_START"   select="12"/>
<xsl:variable name="STRING_END"     select="13"/>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Collect tag libraries
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:function name="xst:import-taglibs" as="element(xst:taglib)*">
  <xsl:param name="imports" as="element(xst:import)*"/>

  <xsl:for-each select="$imports">
    <xsl:variable name="taglib" as="element(xst:taglib)?"
            select="document(@href, /)/xst:taglib"/>

    <xsl:choose>
    <xsl:when test="fn:exists($taglib)">
      <xsl:sequence select="($taglib,
              xst:import-taglibs(
                  $taglib//xst:import[fn:empty(@for) or @for eq 'xstag']))"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:message terminate="yes">
        <xsl:text>Could not import </xsl:text>
        <xsl:value-of select="@href"/>
      </xsl:message>
    </xsl:otherwise>
    </xsl:choose>
  </xsl:for-each>
</xsl:function>

<xsl:function name="xst:extract-tagdefs" as="element(xst:tag)*">
  <xsl:param name="taglibs"    as="element(xst:taglib)*"/>
  <xsl:param name="namespaces" as="element()*"/>

  <xsl:for-each select="$taglibs/xst:tag">
    <xsl:variable name="lib-prefix" select="fn:substring-before(@name,':')"/>
    <xsl:variable name="ns-uri"
            select="fn:namespace-uri-for-prefix($lib-prefix, /*)"/>
    <xsl:variable name="ns-def"     select="$namespaces[@uri=$ns-uri]"/>

    <xsl:if test="fn:exists($ns-def)">
      <xsl:variable name="local-name" select="fn:substring-after(@name,':')"/>
      <xsl:variable name="imp-prefix" select="$ns-def/@prefix"/>
      <xsl:copy>
          <xsl:attribute name="ns-uri"     select="$ns-uri"/>
          <xsl:attribute name="lib-prefix" select="$lib-prefix"/>
          <xsl:attribute name="local-name" select="$local-name"/>
          <xsl:attribute name="ns-uri"     select="$ns-uri"/>
          <xsl:attribute name="imp-prefix" select="$imp-prefix"/>
          <xsl:attribute name="imp-qname"
                select="fn:concat($imp-prefix,':',$local-name)"/>
          <xsl:copy-of select="@*"/>
          <xsl:copy-of select="xst:param"/>
      </xsl:copy>
    </xsl:if>
  </xsl:for-each>
</xsl:function>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Tag call templates and functions
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:template match="*" mode="xst:caller-param">
  <xsl:param name="tag-name"           as="xs:string"/>
  <xsl:param name="called-tag"         as="element(xst:tag)?"/>
  <xsl:param name="handler-names"      as="xs:string+"/>
  <xsl:param name="handler-containers" as="element()+"/>
  <xsl:param name="is-call"            as="xs:boolean"/>

  <xsl:variable name="call-count" as="xs:integer"
        select="xst:count-calls(., $tag-name)"/>

  <xsl:variable name="caller-variables" as="element(xst:variable)*"
        select="xst:find-caller-variables(
            $tag-name, ., $called-tag, $handler-names, $handler-containers, $is-call)"/>

  <osl:with-param name="xst:caller" as="element(xst:caller)">
    <xst:caller tag="{$tag-name}" call="{$call-count}" module-uri="{$xst:module-uri}">
      <xsl:for-each select="$caller-variables">
        <xsl:variable name="caller-variable" as="element(xst:variable)" select="."/>

        <xst:state name="{$caller-variable/@name}">
          <osl:for-each select="${$caller-variable/@name}">
            <xst:item>
              <osl:copy-of select="."/>
            </xst:item>
          </osl:for-each>
        </xst:state>
      </xsl:for-each>

      <xsl:for-each select="$handler-names">
        <xst:handler name="{.}"/>
      </xsl:for-each>
    </xst:caller>
  </osl:with-param>
</xsl:template>

<xsl:template match="*" mode="xst:handlerdef">
  <xsl:param name="tagdefs"      as="element(xst:tag)*"/>
  <xsl:param name="tag-name"     as="xs:string"/>
  <xsl:param name="call-count"   as="xs:integer"/>
  <xsl:param name="handlerdefs"  as="element(xst:param)*"/>
  <xsl:param name="handler-name" as="xs:string?"/>
  <xsl:param name="is-call"      as="xs:boolean"/>

  <xsl:variable name="handlerdef" as="element(xst:param)?"
        select="$handlerdefs[@name eq $handler-name]"/>

  <xsl:variable name="handler-varnames" as="xs:string*"
        select="xst:find-handler-varnames($tag-name, ., $handlerdef, $is-call)"/>

  <osl:template match="xst:handler[
            @name          eq '{$handler-name}' and
            ../@tag        eq '{$tag-name}'     and
            ../@call       eq '{$call-count}'   and
            ../@module-uri eq '{$xst:module-uri}']"
        mode="xst:handle">

    <xsl:apply-templates select="$handlerdef" mode="xst:handler-signature"/>

    <xsl:for-each select="$handler-varnames">
      <xsl:variable name="handler-varname" as="xs:string" select="."/>

      <osl:variable name="{$handler-varname}"
            select="../xst:state[@name eq '{$handler-varname}']/xst:item/node()"/>
    </xsl:for-each>

    <xsl:apply-templates select="node()" mode="xst:convert">
      <xsl:with-param name="tagdefs" select="$tagdefs"/>
    </xsl:apply-templates>
  </osl:template>
</xsl:template>

<xsl:function name="xst:count-calls" as="xs:integer">
  <xsl:param name="caller"   as="element()"/>
  <xsl:param name="tag-name" as="xs:string"/>

  <xsl:sequence select="fn:count($caller/preceding::*[
        fn:name(.) eq $tag-name or
        (self::xst:call and @tag eq $tag-name)
        ]) + 1"/>
</xsl:function>

<xsl:function name="xst:find-caller-variables" as="element(xst:variable)*">
  <xsl:param name="tag-name"           as="xs:string"/>
  <xsl:param name="caller"             as="element()"/>
  <xsl:param name="called-tag"         as="element(xst:tag)?"/>
  <xsl:param name="handler-names"      as="xs:string+"/>
  <xsl:param name="handler-containers" as="element()+"/>
  <xsl:param name="is-call"            as="xs:boolean"/>

  <xsl:variable name="handler-references" as="xs:string*">
    <xsl:for-each select="$handler-containers">
      <xsl:variable name="handler-container" as="element()" select="."/>

      <xsl:variable name="handler-name" as="xs:string">
        <xsl:choose>
        <xsl:when test="$handler-container/self::xst:with-handler">
          <xsl:value-of select="fn:string(@name)"/>
        </xsl:when>
        <xsl:when test="$handler-container is $caller">
          <xsl:value-of select="$handler-names[1]"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="fn:local-name($handler-container)"/>
        </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>

      <xsl:variable name="handlerdef" as="element(xst:param)"
            select="$called-tag/xst:param[@name eq $handler-name]"/>

      <xsl:sequence
            select="xst:find-handler-varnames(
                $tag-name, $handler-container, $handlerdef, $is-call)"/>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="caller-references" as="xs:string*"
        select="fn:distinct-values($handler-references)"/>

  <xsl:for-each select="$caller-references">
    <xsl:variable name="caller-reference" as="xs:string" select="."/>

    <xsl:sequence select="$caller/ancestor-or-self::* /
              preceding-sibling::xst:variable[@name eq $caller-reference][1]"/>
  </xsl:for-each>
</xsl:function>

<xsl:function name="xst:find-handler-varnames" as="xs:string*">
  <xsl:param name="tag-name"          as="xs:string"/>
  <xsl:param name="handler-container" as="element()"/>
  <xsl:param name="handlerdef"        as="element(xst:param)"/>
  <xsl:param name="is-call"           as="xs:boolean"/>

  <xsl:variable name="handled-params" as="xs:string*"
        select="if (fn:not($is-call))
            then fn:tokenize(
                $handler-container/translate(@xst:handles,'$',''),
                '[\s,]+'
                )
            else ()"/>

  <xsl:variable name="param-names" as="xs:string*"
        select="xst:parse-handler-param-names($handlerdef/@as)"/>

  <xsl:variable name="contain-ancestors" as="element()+"
        select="$handler-container/ancestor-or-self::*"/>

  <xsl:variable name="localdefs" as="element()*"
        select="$handler-container//xst:variable"/>

  <xsl:variable name="selectors" as="element()*"
        select="$handler-container//(xst:attribute[@select]|xst:content)"/>

  <xsl:variable name="attribute-templates" as="attribute()*"
        select="($handler-container//* except $selectors)/@*
            [fn:matches(.,'\{.*\}')]"/>

  <xsl:variable name="attribute-containers" as="element()*"
        select="$attribute-templates/.."/>

  <xsl:variable name="content-containers" as="element()*"
        select="$handler-container//text()[fn:matches(.,'\{.*\}')]/.."/>

  <xsl:for-each select="$selectors|$attribute-containers|$content-containers">
    <xsl:variable name="localref" as="element()" select="."/>

    <xsl:variable name="local-ancestors" as="element()*"
          select="$localref/ancestor-or-self::* except $contain-ancestors"/>

    <xsl:variable name="scopedefs" as="element()*"
          select="$localdefs[following-sibling::* = $local-ancestors]"/>

    <xsl:variable name="scope-names" as="xs:string*"
          select="fn:distinct-values($scopedefs/string(@name))"/>

    <xsl:variable name="varlist" as="xs:string*">
      <xsl:if test="$localref = $selectors">
        <xsl:sequence select="$localref/@select /
                xst:extract-select-attribute-variables(.)"/>
      </xsl:if>

      <xsl:if test="$localref = $attribute-containers">
        <xsl:sequence select="($localref/@* intersect $attribute-templates) /
                xst:extract-attribute-template-variables(.)"/>
      </xsl:if>

      <xsl:if test="$localref = $content-containers">
        <xsl:sequence select="$localref/text() /
                xst:extract-content-template-variables(.)"/>
      </xsl:if>
    </xsl:variable>

    <xsl:variable name="varnames" select="fn:distinct-values($varlist)"/>
 
    <xsl:variable name="paramrefs" select="$param-names[. = $varnames]"/>

    <xsl:if test="fn:not($is-call) and fn:exists($paramrefs[not(. = $handled-params)])">
      <xsl:message terminate="yes">
        <xsl:value-of select="$tag-name"/>
        <xsl:text> with </xsl:text>
        <xsl:value-of select="$handlerdef/string(@name)"/>
        <xsl:text> handler without variables in handles attribute: </xsl:text>
        <xsl:value-of select="fn:string-join($paramrefs[not(. = $handled-params)],', ')"/>
      </xsl:message>
    </xsl:if> 

    <xsl:sequence select="$varnames[fn:not(. = ($paramrefs, $scope-names))]"/>
  </xsl:for-each>
</xsl:function>

<xsl:function name="xst:cast-as" as="xs:string?">
  <xsl:param name="expression" as="xs:string?"/>
  <xsl:param name="type"       as="xs:string?"/>

  <xsl:choose>
  <xsl:when test="fn:string-length($type) gt 0 and
          fn:not(fn:matches($type,
'^\s*(attribute|comment|document-node|element|processing-instruction|text)\s*\(\s*\)'
          ))">
     <xsl:sequence select="fn:concat('(',$expression,') cast as ', $type)"/>
   </xsl:when>
   <xsl:otherwise>
     <xsl:value-of select="$expression"/>
   </xsl:otherwise>
   </xsl:choose>
</xsl:function>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Handler parsing functions
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:template match="element(xst:param)" mode="xst:handler-signature">
  <xsl:variable name="handler-signature" as="xs:string*"
        select="xst:parse-handler-signature(@as)"/>

  <xsl:variable name="handler-return-type" as="xs:string"
        select="$handler-signature[1]"/>

  <xsl:variable name="handler-param-count" as="xs:integer"
        select="fn:count($handler-signature) idiv 2"/>

  <xsl:if test="fn:string-length($handler-return-type) gt 0">
    <xsl:attribute name="as" select="$handler-return-type"/>
  </xsl:if>

  <xsl:for-each select="1 to $handler-param-count">
    <xsl:variable name="i"          as="xs:integer" select=". * 2"/>
    <xsl:variable name="param-name" as="xs:string"
          select="$handler-signature[$i]"/>
    <xsl:variable name="param-type" as="xs:string?"
          select="$handler-signature[$i + 1]"/>

    <osl:param name="{$param-name}">
      <xsl:if test="fn:string-length($param-type) gt 0">
        <xsl:attribute name="as" select="$param-type"/>
      </xsl:if>
    </osl:param>
  </xsl:for-each>
</xsl:template>

<xsl:function name="xst:parse-handler-param-names" as="xs:string*">
  <xsl:param name="handler" as="attribute()?"/>

  <xsl:variable name="value" as="xs:string?"
      select="fn:normalize-space($handler)"/>

  <xsl:if test="fn:string-length($value) gt 0">
    <xsl:variable name="handler-tokens" as="xs:string*"
        select="xst:tokenize-handler($value)"/>

    <xsl:if test="fn:exists($handler-tokens)">
      <xsl:variable name="handler-signature" as="xs:integer*"
          select="xst:find-handler($handler-tokens,1)"/>

      <xsl:if test="fn:exists($handler-signature)">
        <xsl:sequence select="xst:extract-handler-param-names(
                $handler-tokens,$handler-signature)"/>
      </xsl:if>
    </xsl:if>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:parse-handler-signature" as="xs:string*">
  <xsl:param name="handler" as="attribute()?"/>

  <xsl:variable name="value" as="xs:string?"
      select="fn:normalize-space($handler)"/>

  <xsl:if test="fn:string-length($value) gt 0">
    <xsl:variable name="handler-tokens" as="xs:string*"
        select="xst:tokenize-handler($value)"/>

    <xsl:if test="fn:exists($handler-tokens)">
      <xsl:variable name="handler-signature" as="xs:integer*"
          select="xst:find-handler($handler-tokens,1)"/>

      <xsl:if test="fn:exists($handler-signature)">
        <xsl:sequence select="xst:extract-handler-signature(
                $handler-tokens,$handler-signature)"/>
      </xsl:if>
    </xsl:if>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:parse-handler-params" as="xs:string*">
  <xsl:param name="handler" as="attribute()?"/>

  <xsl:variable name="value" as="xs:string?"
      select="fn:normalize-space($handler)"/>

  <xsl:if test="fn:string-length($value) gt 0">
    <xsl:variable name="handler-tokens" as="xs:string*"
        select="xst:tokenize-handler($value)"/>

    <xsl:if test="fn:exists($handler-tokens)">
      <xsl:variable name="handler-signature" as="xs:integer*"
          select="xst:find-handler($handler-tokens,1)"/>

      <xsl:if test="fn:exists($handler-signature)">
        <xsl:sequence select="xst:extract-handler-params(
                $handler-tokens,$handler-signature,fn:count($handler-signature))"/>
      </xsl:if>
    </xsl:if>
  </xsl:if>
</xsl:function>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Handler token extraction functions
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:function name="xst:extract-handler-signature" as="xs:string*">
  <xsl:param name="handler-tokens"    as="xs:string*"/>
  <xsl:param name="handler-signature" as="xs:integer*"/>

  <xsl:variable name="signature-last" as="xs:integer"
      select="fn:count($handler-signature)"/>

  <xsl:choose>
  <xsl:when test="$handler-signature[$signature-last - 1] eq $RETURN_TYPE">
      <xsl:sequence select="fn:string-join(
              $handler-tokens[
                $handler-signature[$signature-last] le fn:position()],
              ''),
              xst:extract-handler-params(
                  $handler-tokens,$handler-signature,$signature-last - 2)"/>
  </xsl:when>
  <xsl:otherwise>
    <xsl:sequence select="'item()*',
              xst:extract-handler-params(
                  $handler-tokens,$handler-signature,$signature-last)"/>
  </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:extract-handler-params" as="xs:string*">
  <xsl:param name="handler-tokens"    as="xs:string*"/>
  <xsl:param name="handler-signature" as="xs:integer*"/>
  <xsl:param name="signature-last"    as="xs:integer"/>

  <xsl:for-each select="$handler-signature[fn:position() le $signature-last]">
    <xsl:variable name="current" as="xs:integer" select="."/>
    <xsl:variable name="index"   as="xs:integer" select="fn:position()"/>

    <xsl:choose>
    <xsl:when test="$current eq $PARAM_NAME">
      <xsl:choose>
      <xsl:when test="$handler-signature[$index + 2] eq $PARAM_TYPE_START">
        <xsl:sequence
            select="$handler-tokens[$handler-signature[$index + 1]]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:sequence
            select="$handler-tokens[$handler-signature[$index + 1]], 'item()*'"/>
      </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:when test="$current eq $PARAM_TYPE_START and
            $handler-signature[$index + 2] eq $PARAM_TYPE_END">
      <xsl:sequence select="fn:string-join(
              $handler-tokens[
                $handler-signature[$index + 1] le fn:position() and
                fn:position() le $handler-signature[$index + 3]
                ],
              '')"/>
    </xsl:when>
    </xsl:choose>
  </xsl:for-each>
</xsl:function>

<xsl:function name="xst:extract-handler-param-names" as="xs:string*">
  <xsl:param name="handler-tokens"    as="xs:string*"/>
  <xsl:param name="handler-signature" as="xs:integer*"/>

  <xsl:for-each select="$handler-signature">
    <xsl:variable name="current" as="xs:integer" select="."/>
    <xsl:variable name="index"   as="xs:integer" select="fn:position()"/>

    <xsl:if test="$current eq $PARAM_NAME">
      <xsl:sequence
          select="$handler-tokens[$handler-signature[$index + 1]]"/>
    </xsl:if>
  </xsl:for-each>
</xsl:function>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Variable extraction functions
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:function name="xst:extract-select-attribute-variables" as="xs:string*">
  <xsl:param name="attribute" as="attribute()?"/>

  <xsl:if test="fn:exists($attribute)">
    <xsl:variable name="segment-tokens" as="xs:string*"
        select="xst:tokenize-segments(fn:string($attribute))"/>

    <xsl:if test="fn:exists($segment-tokens)">
      <xsl:variable name="wrapped-tokens" as="xs:string+"
          select="'{', $segment-tokens, '}'"/>

      <xsl:sequence
          select="xst:extract-next-expression-variables($wrapped-tokens,1)"/>
    </xsl:if>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:extract-attribute-template-variables" as="xs:string*">
  <xsl:param name="attribute" as="attribute()?"/>

  <xsl:if test="fn:exists($attribute)">
    <xsl:variable name="segment-tokens" as="xs:string*"
        select="xst:tokenize-segments(fn:string($attribute))"/>

    <xsl:if test="fn:exists($segment-tokens)">
      <xsl:sequence
          select="xst:extract-next-expression-variables($segment-tokens,1)"/>
    </xsl:if>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:extract-content-template-variables" as="xs:string*">
  <xsl:param name="text" as="text()?"/>

  <xsl:if test="fn:exists($text)">
    <xsl:variable name="segment-tokens" as="xs:string*"
        select="xst:tokenize-segments(fn:string($text))"/>

    <xsl:if test="fn:exists($segment-tokens)">
      <xsl:sequence
          select="xst:extract-next-expression-variables($segment-tokens,1)"/>
    </xsl:if>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:extract-next-expression-variables" as="xs:string*">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="start-token"    as="xs:integer"/>

  <xsl:variable name="next-expression" as="xs:integer*"
      select="xst:find-next-expression($segment-tokens,$start-token)"/>

  <xsl:if test="fn:exists($next-expression)">
    <xsl:variable name="expression-end-token" as="xs:integer"
        select="$next-expression[fn:last() - 1]"/>

    <xsl:sequence select="xst:extract-expression-variables(
            $segment-tokens, $next-expression, 1)"/>

    <xsl:sequence select="xst:extract-next-expression-variables(
            $segment-tokens,$expression-end-token + 1)"/>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:extract-expression-variables" as="xs:string*">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="boundaries"     as="xs:integer*"/>
  <xsl:param name="end-boundary"   as="xs:integer"/>

  <xsl:variable name="first-token" as="xs:integer"
      select="$boundaries[$end-boundary] + 1"/>

  <xsl:variable name="last-token" as="xs:integer?"
      select="$boundaries[$end-boundary + 2] - 1"/>

  <xsl:variable name="next-end" as="xs:integer"
      select="$end-boundary + 4"/>

  <xsl:for-each select="$segment-tokens[
          $first-token le fn:position() and
          $last-token ge fn:position()]">
    <xsl:variable name="token" as="xs:string" select="."/>

    <xsl:variable name="regex" as="xs:string">
      <xsl:text>\$[A-Za-z_][A-Za-z0-9_.-]*(:[A-Za-z_][A-Za-z0-9_.-])?</xsl:text>
    </xsl:variable>

    <xsl:analyze-string regex="{$regex}" select="$token">
      <xsl:matching-substring>
        <xsl:variable name="match" as="xs:string" select="."/>

        <xsl:value-of select="fn:substring($match,2)"/>
      </xsl:matching-substring>
    </xsl:analyze-string>
  </xsl:for-each>

  <xsl:if test="$next-end lt fn:count($boundaries)">
    <xsl:sequence select="xst:extract-expression-variables(
            $segment-tokens, $boundaries, $next-end)"/>
  </xsl:if>
</xsl:function>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Low-level handler parsing functions
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:function name="xst:find-handler" as="xs:integer*">
  <xsl:param name="function-tokens" as="xs:string*"/>
  <xsl:param name="start-token"     as="xs:integer"/>

  <xsl:variable name="current-token" as="xs:string?"
      select="$function-tokens[$start-token]"/>

  <xsl:if test="$current-token eq 'handler'">
    <xsl:sequence select="xst:find-paramlist(
            $function-tokens,$start-token + 1,())"/>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:find-paramlist" as="xs:integer*">
  <xsl:param name="function-tokens" as="xs:string*"/>
  <xsl:param name="start-token"     as="xs:integer"/>
  <xsl:param name="signature"       as="xs:integer*"/>

  <xsl:variable name="current-token" as="xs:string?"
      select="$function-tokens[$start-token]"/>

  <xsl:choose>
  <xsl:when test="$current-token eq ' '">
    <xsl:sequence select="xst:find-paramlist(
            $function-tokens,$start-token + 1,$signature)"/>
  </xsl:when>
  <xsl:when test="$current-token eq '('">
    <xsl:sequence select="xst:next-param(
            $function-tokens,$start-token + 1,$signature)"/>
  </xsl:when>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:next-param" as="xs:integer*">
  <xsl:param name="function-tokens" as="xs:string*"/>
  <xsl:param name="start-token"     as="xs:integer"/>
  <xsl:param name="signature"       as="xs:integer*"/>

  <xsl:variable name="current-token" as="xs:string?"
      select="$function-tokens[$start-token]"/>

  <xsl:choose>
  <xsl:when test="$current-token = (' ', '$')">
    <xsl:sequence select="xst:next-param(
            $function-tokens,$start-token + 1,$signature)"/>
  </xsl:when>
  <xsl:when test="fn:matches($current-token,'^[A-Za-z_]')">
    <xsl:sequence select="xst:next-param-type(
            $function-tokens,$start-token + 1,
            ($signature,$PARAM_NAME,$start-token))"/>
  </xsl:when>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:next-param-type" as="xs:integer*">
  <xsl:param name="function-tokens" as="xs:string*"/>
  <xsl:param name="start-token"     as="xs:integer"/>
  <xsl:param name="signature"       as="xs:integer*"/>

  <xsl:variable name="current-token" as="xs:string?"
      select="$function-tokens[$start-token]"/>

  <xsl:choose>
  <xsl:when test="$current-token eq ' '">
    <xsl:sequence select="xst:next-param-type(
            $function-tokens,$start-token + 1,$signature)"/>
  </xsl:when>
  <xsl:when test="$current-token eq ','">
    <xsl:sequence select="xst:next-param(
            $function-tokens,$start-token + 1,$signature)"/>
  </xsl:when>
  <xsl:when test="$current-token eq ')'">
    <xsl:sequence select="xst:find-return-type(
            $function-tokens,$start-token + 1,$signature)"/>
  </xsl:when>
  <xsl:when test="$current-token eq 'as' and
          fn:count($function-tokens) ge ($start-token + 2) and
          $function-tokens[$start-token + 1] eq ' ' and
          fn:matches($function-tokens[$start-token + 2],'^[A-Za-z_]')">
    <xsl:sequence select="xst:next-param-end(
            $function-tokens,1,$start-token + 3,
            $start-token + 2,$signature)"/>
  </xsl:when>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:next-param-end" as="xs:integer*">
  <xsl:param name="function-tokens"  as="xs:string*"/>
  <xsl:param name="depth"            as="xs:integer"/>
  <xsl:param name="start-token"      as="xs:integer"/>
  <xsl:param name="param-type-start" as="xs:integer"/>
  <xsl:param name="signature"        as="xs:integer*"/>

  <xsl:variable name="current-token" as="xs:string?"
      select="$function-tokens[$start-token]"/>

  <xsl:choose>
  <xsl:when test="$current-token = (' ', '?', '*', '+')">
    <xsl:sequence select="xst:next-param-end(
            $function-tokens,$depth,$start-token + 1,$param-type-start,$signature)"/>
  </xsl:when>
  <xsl:when test="$current-token eq '('">
    <xsl:sequence select="xst:next-param-end(
            $function-tokens,$depth + 1,$start-token + 1,$param-type-start,$signature)"/>
  </xsl:when>
  <xsl:when test="$depth gt 1">
    <xsl:choose>
    <xsl:when test="$current-token eq ')'">
      <xsl:sequence select="xst:next-param-end(
              $function-tokens,$depth - 1,$start-token + 1,$param-type-start,$signature)"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:sequence select="xst:next-param-end(
              $function-tokens,$depth,$start-token + 1,$param-type-start,$signature)"/>
    </xsl:otherwise>
    </xsl:choose>
  </xsl:when>
  <xsl:when test="$depth eq 1">
    <xsl:choose>
    <xsl:when test="$current-token = (',', ')')">
      <xsl:variable name="param-type-end" as="xs:integer">
         <xsl:choose>
         <xsl:when test="$function-tokens[$start-token - 1] eq ' '">
           <xsl:value-of select="$start-token - 2"/>
         </xsl:when>
         <xsl:otherwise>
           <xsl:value-of select="$start-token - 1"/>
         </xsl:otherwise>
         </xsl:choose>
      </xsl:variable>

      <xsl:variable name="next-signature" select="($signature,
              $PARAM_TYPE_START,$param-type-start,
              $PARAM_TYPE_END,$param-type-end)"/>

      <xsl:choose>
      <xsl:when test="$current-token eq ','">
        <xsl:sequence select="xst:next-param(
                $function-tokens,$start-token + 1,$next-signature)"/>
      </xsl:when>
      <xsl:when test="$current-token eq ')'">
        <xsl:sequence select="xst:find-return-type(
                $function-tokens,$start-token + 1,$next-signature)"/>
      </xsl:when>
      </xsl:choose>
    </xsl:when>
    </xsl:choose>
  </xsl:when>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:find-return-type" as="xs:integer*">
  <xsl:param name="function-tokens" as="xs:string*"/>
  <xsl:param name="start-token"     as="xs:integer"/>
  <xsl:param name="signature"       as="xs:integer*"/>

  <xsl:variable name="token-count" as="xs:integer"
      select="fn:count($function-tokens)"/>

  <xsl:choose>
  <xsl:when test="$start-token gt $token-count">
    <xsl:sequence select="$signature"/>
  </xsl:when>
  <xsl:otherwise>
    <xsl:variable name="current-token" as="xs:string?"
        select="$function-tokens[$start-token]"/>

    <xsl:choose>
    <xsl:when test="$current-token eq ' '">
      <xsl:sequence select="xst:find-return-type(
              $function-tokens,$start-token + 1,$signature)"/>
    </xsl:when>
    <xsl:when test="$current-token eq 'as' and
            $token-count ge ($start-token + 2) and
            $function-tokens[$start-token + 1] eq ' ' and
            fn:matches($function-tokens[$start-token + 2],'^[A-Za-z_]')">
      <xsl:sequence select="($signature,$RETURN_TYPE,$start-token + 2)"/>
    </xsl:when>
    </xsl:choose>
  </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:find-token" as="xs:integer?">
  <xsl:param name="token-sequence" as="xs:string*"/>
  <xsl:param name="match-tokens"   as="xs:string+"/>
  <xsl:param name="current-token"  as="xs:integer"/>

  <xsl:choose>
  <xsl:when test="$token-sequence[$current-token] = $match-tokens">
    <xsl:sequence select="$current-token"/>
  </xsl:when>
  <xsl:when test="$current-token lt fn:count($token-sequence)">
    <xsl:sequence
        select="xst:find-token($token-sequence, $match-tokens, $current-token + 1)"/>
  </xsl:when>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:tokenize-handler" as="xs:string*">
  <xsl:param name="text" as="xs:string?"/>

  <xsl:variable name="regex" as="xs:string">
    <xsl:text
>\(|\)|\s+|\$|,|[A-Za-z_][A-Za-z0-9_.-]*(:[A-Za-z_][A-Za-z0-9_.-]*)?|\?|\*|\+</xsl:text>
  </xsl:variable>

  <xsl:analyze-string regex="{$regex}" select="$text">
    <xsl:matching-substring>
      <xsl:value-of select="."/>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:value-of select="."/>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:function>


<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Attribute conversion functions
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:function name="xst:convert-attribute-value" as="xs:string?">
  <xsl:param name="attribute" as="attribute()?"/>

  <xsl:variable name="value" select="fn:string($attribute)"/>

  <xsl:if test="fn:string-length($value) gt 0">
    <xsl:variable name="segment-tokens" as="xs:string*"
        select="xst:tokenize-segments($value)"/>

    <xsl:choose>
    <xsl:when test="fn:exists($segment-tokens)">
      <xsl:variable name="expression" as="xs:string*"
          select="xst:convert-next-attribute-expression($segment-tokens,1)"/>

      <xsl:choose>
      <xsl:when test="fn:empty($expression)">
        <xsl:sequence select="$value"/>
      </xsl:when>
      <xsl:when test="fn:count($expression) gt 1">
        <xsl:sequence
            select="fn:concat('fn:concat(',fn:string-join($expression,','),')')"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:sequence select="$expression"/>
      </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <xsl:sequence select="$value"/>
    </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:convert-next-attribute-expression" as="xs:string*">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="start-token"    as="xs:integer"/>

  <xsl:variable name="next-expression" as="xs:integer*"
      select="xst:find-next-expression($segment-tokens,$start-token)"/>

  <xsl:choose>
  <xsl:when test="fn:exists($next-expression)">
    <xsl:variable name="expression-start" as="xs:integer"
        select="$next-expression[1]"/>

    <xsl:variable name="expression-end" as="xs:integer"
        select="$next-expression[fn:last() - 1]"/>

    <xsl:if test="$start-token lt $expression-start">
      <xsl:value-of select="xst:quote-tokens(
              $segment-tokens, $start-token, $expression-start - 1)"/>
    </xsl:if>

    <xsl:value-of select="xst:join-segments($segment-tokens,$next-expression)"/>

    <xsl:sequence select="xst:convert-next-attribute-expression(
            $segment-tokens, $expression-end + 1)"/>
  </xsl:when>
  <xsl:otherwise>
    <xsl:variable name="last-token" as="xs:integer"
        select="fn:count($segment-tokens)"/>

    <xsl:if test="$start-token le $last-token">
      <xsl:value-of select="xst:quote-tokens(
              $segment-tokens, $start-token, $last-token)"/>
    </xsl:if>
  </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Content conversion functions
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:function name="xst:convert-content-expressions" as="node()*">
  <xsl:param name="text" as="text()?"/>

  <xsl:variable name="value" select="fn:string($text)"/>

  <xsl:choose>
  <xsl:when test="fn:matches($value,'\{.*\}','s')">
    <xsl:variable name="segment-tokens" as="xs:string*"
        select="xst:tokenize-segments($value)"/>

    <xsl:choose>
    <xsl:when test="fn:exists($segment-tokens)">
      <xsl:sequence
          select="xst:convert-next-content-expression($segment-tokens,1)"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:sequence select="$text"/>
    </xsl:otherwise>
    </xsl:choose>
  </xsl:when>
  <xsl:otherwise>
    <xsl:sequence select="$text"/>
  </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:convert-next-content-expression" as="node()*">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="start-token"    as="xs:integer"/>

  <xsl:variable name="next-expression" as="xs:integer*"
      select="xst:find-next-expression($segment-tokens,$start-token)"/>

  <xsl:choose>
  <xsl:when test="fn:exists($next-expression)">
    <xsl:variable name="expression-start" as="xs:integer"
        select="$next-expression[1]"/>

    <xsl:variable name="expression-end" as="xs:integer"
        select="$next-expression[fn:last() - 1]"/>

    <xsl:if test="$start-token lt $expression-start">
      <xsl:value-of select="xst:join-tokens(
              $segment-tokens, $start-token, $expression-start - 1)"/>
    </xsl:if>

    <osl:copy-of>
      <xsl:attribute name="select"
          select="xst:join-segments($segment-tokens,$next-expression)"/>
    </osl:copy-of>

    <xsl:sequence select="xst:convert-next-content-expression(
            $segment-tokens,$expression-end + 1)"/>
  </xsl:when>
  <xsl:otherwise>
    <xsl:variable name="last-token" as="xs:integer"
        select="fn:count($segment-tokens)"/>

    <xsl:if test="$start-token le $last-token">
      <xsl:value-of select="xst:join-tokens(
              $segment-tokens,$start-token,$last-token)"/>
    </xsl:if>
  </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  - Low level parsing and conversion functions
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<xsl:function name="xst:quote-tokens" as="xs:string?">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="start-token"    as="xs:integer"/>
  <xsl:param name="end-token"      as="xs:integer"/>

  <xsl:variable name="token" as="xs:string"
        select="xst:join-tokens($segment-tokens, $start-token, $end-token)"/>

  <xsl:choose>
  <xsl:when test="fn:empty($token)"/>
  <xsl:when test="fn:matches($token,'^[0-9]+(\.[0-9]+)?$')">
    <xsl:value-of select="$token"/>
  </xsl:when>
  <xsl:otherwise>
    <xsl:value-of select="fn:concat($APOSTROPHE,$token,$APOSTROPHE)"/>
  </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:join-segments" as="xs:string?">
  <xsl:param name="segment-tokens"   as="xs:string*"/>
  <xsl:param name="boundaries"       as="xs:integer+"/>

  <xsl:variable name="segment-count" as="xs:integer"
        select="(fn:count($boundaries) idiv 2) - 1"/>

  <xsl:variable name="segments" as="xs:string*">
    <xsl:for-each select="1 to $segment-count">
      <xsl:variable name="pair"             as="xs:integer" select="."/>
      <xsl:variable name="i"                as="xs:integer" select="$pair * 2"/>
      <xsl:variable name="segment-start"    as="xs:integer"
            select="$boundaries[$i - 1]"/>
      <xsl:variable name="segment-boundary" as="xs:integer"
            select="$boundaries[$i]"/>
      <xsl:variable name="segment-end"      as="xs:integer"
            select="$boundaries[$i + 1]"/>

      <xsl:variable name="join-start" as="xs:integer">
        <xsl:choose>
        <xsl:when test="$pair eq 1">
          <xsl:value-of select="$segment-start + 1"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$segment-start"/>
        </xsl:otherwise>
        </xsl:choose>
      </xsl:variable>

      <xsl:value-of select="xst:join-tokens(
              $segment-tokens,
              $join-start,
              $segment-end - 1,
              ($segment-boundary eq $STRING_START)
              )"/>
    </xsl:for-each>
  </xsl:variable>

  <xsl:choose>
  <xsl:when test="fn:empty($segments)"/>
  <xsl:when test="fn:count($segments) eq 1">
    <xsl:value-of select="$segments"/>
  </xsl:when>
  <xsl:otherwise>
    <xsl:value-of select="fn:string-join($segments,'')"/>
  </xsl:otherwise>
  </xsl:choose>
</xsl:function>


<xsl:function name="xst:join-tokens" as="xs:string?">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="start-token"    as="xs:integer"/>
  <xsl:param name="end-token"      as="xs:integer"/>

  <xsl:sequence select="xst:join-tokens(
          $segment-tokens,$start-token,$end-token,fn:false())"/>
</xsl:function>

<xsl:function name="xst:join-tokens" as="xs:string?">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="start-token"    as="xs:integer"/>
  <xsl:param name="end-token"      as="xs:integer"/>
  <xsl:param name="is-string"      as="xs:boolean"/>

  <xsl:variable name="tokens" as="xs:string*">
    <xsl:for-each select="$segment-tokens[
            fn:position() ge $start-token and
            fn:position() le $end-token]">
      <xsl:variable name="token" as="xs:string" select="."/>

      <xsl:choose>
      <xsl:when test="$is-string">
        <xsl:value-of select="$token"/>
      </xsl:when>
      <xsl:when test="$token eq '{{'">
        <xsl:text>{</xsl:text>
      </xsl:when>
      <xsl:when test="$token eq '}}'">
        <xsl:text>}</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$token"/>
      </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:variable>

  <xsl:choose>
  <xsl:when test="fn:empty($tokens)"/>
  <xsl:when test="fn:count($tokens) eq 1">
    <xsl:value-of select="$tokens"/>
  </xsl:when>
  <xsl:otherwise>
    <xsl:value-of select="fn:string-join($tokens,'')"/>
  </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<xsl:function name="xst:find-next-expression" as="xs:integer*">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="start-token"    as="xs:integer"/>

  <xsl:variable name="expression-start" as="xs:integer?"
      select="xst:find-token($segment-tokens, '{', $start-token)"/>

  <xsl:if test="$expression-start gt 0">
    <xsl:sequence select="xst:find-expression-boundaries(
            $segment-tokens,
            ($expression-start, $SEQUENCE_START),
            $expression-start + 1)"/>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:find-expression-boundaries" as="xs:integer*">
  <xsl:param name="segment-tokens" as="xs:string*"/>
  <xsl:param name="boundaries"     as="xs:integer+"/>
  <xsl:param name="start-token"    as="xs:integer"/>

  <xsl:variable name="boundary-position" as="xs:integer?"
      select="xst:find-token(
          $segment-tokens,
          ('}', $APOSTROPHE, $QUOTATION_MARK),
          $start-token + 1)"/>

  <xsl:if test="$boundary-position gt 0">
    <xsl:variable name="boundary-token" as="xs:string?"
        select="$segment-tokens[$boundary-position]"/>

    <xsl:choose>
    <xsl:when test="$boundary-token eq '}'">
      <xsl:sequence select="$boundaries, $boundary-position, $SEQUENCE_END"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="string-end" as="xs:integer?"
          select="xst:find-token(
              $segment-tokens, $boundary-token, $boundary-position + 1)"/>

      <xsl:if test="$string-end gt 0">
        <xsl:sequence select="xst:find-expression-boundaries(
                $segment-tokens,
                ($boundaries, $boundary-position, $STRING_START, $string-end, $STRING_END),
                $string-end + 1)"/>
      </xsl:if>
    </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
</xsl:function>

<xsl:function name="xst:tokenize-segments" as="xs:string*">
  <xsl:param name="text" as="xs:string?"/>

  <xsl:variable name="regex" as="xs:string">
    <xsl:text>\{\{?|\}\}?|'|"</xsl:text>
  </xsl:variable>

  <xsl:analyze-string regex="{$regex}" select="$text">
    <xsl:matching-substring>
      <xsl:value-of select="."/>
    </xsl:matching-substring>
    <xsl:non-matching-substring>
      <xsl:value-of select="."/>
    </xsl:non-matching-substring>
  </xsl:analyze-string>
</xsl:function>

</xsl:stylesheet>